/*
 * Copyright (C) 2018 The Asiainfo-Ability Authors
 *
 *      Licensed under the Apache License, Version 2.0 (the "License");
 *      you may not use this file except in compliance with the License.
 *      You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *      Unless required by applicable law or agreed to in writing, software
 *      distributed under the License is distributed on an "AS IS" BASIS,
 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *      See the License for the specific language governing permissions and
 *      limitations under the License.
 */

package org.asiainfo.ability.base.service;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asiainfo.ability.base.async.CustomQueue;
import org.asiainfo.ability.base.vo.CalleeTrace;
import org.asiainfo.ability.base.vo.CallerTrace;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @Author: visen
 * @Date: 2018/2/6
 * @Description:
 */
@SuppressWarnings("unchecked")
public class TraceService {
    protected static final Logger log = LogManager.getLogger(TraceService.class);
    private final static String TRACE_CALLEE_DB = "trace_callee_db";
    private final static String TRACE_DB = "trace_db";
    private final static String TRACE_CALLER_DB = "trace_caller_db";
    private final static String MACHINE_STATUS_DB = "machine_status_db";
    private final static String TRACE_CALLER = "trace_caller";
    private final static String TRACE_CALLEE = "trace_callee";
    private final static String TRACE = "trace";
    private final static String MACHINE_STATUS = "machine_status";

    private boolean isActive;
    private EsUtilsBean esUtils;
    private CustomQueue customQueue;


    private void buildTraceCallerTable() {
        if (!esUtils.isExistsDatabase(TRACE_CALLER_DB)) {
            esUtils.createDatabase(TRACE_CALLER_DB);
        }

        if (!esUtils.isExistsTable(TRACE_CALLER_DB, TRACE_CALLER)) {
            List<EsUtilsBean.EsColumnMeta> metas = new ArrayList();
            metas.add(new EsUtilsBean.EsColumnMeta("traceid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("bizid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("span", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("callid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("api", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("serviceName", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("instance", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("begintime", "long"));
            metas.add(new EsUtilsBean.EsColumnMeta("endtime", "long"));
            metas.add(new EsUtilsBean.EsColumnMeta("costtime", "integer"));
            metas.add(new EsUtilsBean.EsColumnMeta("costnettime", "integer"));
            metas.add(new EsUtilsBean.EsColumnMeta("state", "integer"));
            metas.add(new EsUtilsBean.EsColumnMeta("requestAddr", "text"));
            try {
                esUtils.createTable(TRACE_CALLER_DB, TRACE_CALLER, metas);
            } catch (Exception e) {
                log.error("--", e);
            }
        }
    }
    private void buildTraceCalleeTable() {
        if (!esUtils.isExistsDatabase(TRACE_CALLEE_DB)) {
            esUtils.createDatabase(TRACE_CALLEE_DB);
        }

        if (!esUtils.isExistsTable(TRACE_CALLEE_DB, TRACE_CALLEE)) {
            List<EsUtilsBean.EsColumnMeta> metas = new ArrayList();
            metas.add(new EsUtilsBean.EsColumnMeta("traceid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("bizid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("span", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("callid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("request", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("response", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("begintime", "long"));
            metas.add(new EsUtilsBean.EsColumnMeta("endtime", "long"));
            try {
                esUtils.createTable(TRACE_CALLEE_DB, TRACE_CALLEE, metas);
            } catch (Exception e) {
                log.error("--", e);
            }
        }
    }

    private void buildTrace() {
        if (!esUtils.isExistsDatabase(TRACE_DB)) {
            esUtils.createDatabase(TRACE_DB);
        }

        if (!esUtils.isExistsTable(TRACE_DB, TRACE)) {
            List<EsUtilsBean.EsColumnMeta> metas = new ArrayList();
            metas.add(new EsUtilsBean.EsColumnMeta("traceid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("bizid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("span", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("callid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("level", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("content", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("time", "long"));
            try {
                esUtils.createTable(TRACE_DB, TRACE, metas);
            } catch (Exception e) {
                log.error("--", e);
            }
        }
    }
    private void buildMachine() {
        if (!esUtils.isExistsDatabase(MACHINE_STATUS_DB)) {
            esUtils.createDatabase(MACHINE_STATUS_DB);
        }

        if (!esUtils.isExistsTable(MACHINE_STATUS_DB, MACHINE_STATUS)) {
            List<EsUtilsBean.EsColumnMeta> metas = new ArrayList();
            metas.add(new EsUtilsBean.EsColumnMeta("machineid", "text"));
            metas.add(new EsUtilsBean.EsColumnMeta("cpuRatio", "integer"));
            metas.add(new EsUtilsBean.EsColumnMeta("memRatio", "integer"));
            metas.add(new EsUtilsBean.EsColumnMeta("time", "long"));
            try {
                esUtils.createTable(MACHINE_STATUS_DB, MACHINE_STATUS, metas);
            } catch (Exception e) {
                log.error("--", e);
            }
        }
    }
    private static interface TraceAction {
        public void done();
    }

    @Value("${ability.trace.ip}")
    private String ip;

    @Value("${ability.trace.port}")
    private int port;

    @PostConstruct
    private void init() {

        esUtils = new EsUtilsBean(ip, port);
        try {
            buildTraceCallerTable();
            buildTraceCalleeTable();
            buildTrace();
            buildMachine();
        } catch (Exception e) {
            log.error(e);
        }

        customQueue = new CustomQueue(65536, new CustomQueue.Future<TraceAction>() {
            @Override
            public void callback(TraceAction value) {
                value.done();
            }
        });
    }


    @PreDestroy
    public void release() {
        log.error("release...");
        try {
            customQueue.release();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void saveLogTrace(String traceId, String bizId, String callId, String level, String content, long time) {
        customQueue.publish(new TraceAction() {
                public void done() {
                    Map data = new HashMap();
                    data.put("traceid", traceId);
                    data.put("bizid", bizId);
                    data.put("callid", callId);
                    data.put("content", content);
                    data.put("level", level);
                    data.put("time", time);
                    try {
                        esUtils.bulkInsert(TRACE_DB, TRACE, data);
                    } catch (Exception e) {
                        log.error("", e);
                    }
                }
            }
        );

    }
    public static void main(String[] args) {
        CallerTrace callerTrace = new CallerTrace();
        callerTrace.setInstance("123");
        callerTrace.setId("1111");
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        try {
            Map d = mapper.readValue(mapper.writeValueAsBytes(callerTrace), HashMap.class);
            System.out.println(d);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private final static  ObjectMapper mapper = new ObjectMapper();
    static {
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
    public void saveCalleeTrace(CalleeTrace calleeTrace) {
        Map data = new HashMap();
        customQueue.publish(new TraceAction() {
            public void done() {
                try {
                    //Map data = new HashMap();
                    Map data = mapper.readValue(mapper.writeValueAsBytes(calleeTrace), HashMap.class);
                    esUtils.bulkInsert(TRACE_CALLEE_DB, TRACE_CALLEE, data);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    public void saveCallerTrace(CallerTrace callerTrace) {
        Map data = new HashMap();
        customQueue.publish(new TraceAction() {
            public void done() {
                try {
                    Map data = mapper.readValue(mapper.writeValueAsBytes(callerTrace), HashMap.class);
                    esUtils.bulkInsert(TRACE_CALLER_DB, TRACE_CALLER, callerTrace.getId(), data);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public List<Map> searchCalleeTrace(Map<String, Object> conditions, List fetchSources
            , EsUtilsBean.Page page) {
        return esUtils.search(TRACE_CALLEE_DB, TRACE_CALLEE, conditions, fetchSources
                , page);
    }
    public List<Map> search(String database, String table, Map<String, Object> conditions, List fetchSources
            , EsUtilsBean.Page page) {
        return esUtils.search(database, table, conditions, fetchSources
                , page);
    }

    public EsUtilsBean getEsUtils() {
        return esUtils;
    }
}
